﻿
-----------------------------------------------------------------------
-- MatrixTypes.lua
--
-- A Framework for World Of Warcraft addons
-- Copyright (C) 2007  wwkkii
-- 
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
-- 
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-- 
-- Home Page: http://code.google.com/p/wowmatrix
-----------------------------------------------------------------------

local __version__ = { "MatrixTypes", 0, 1, 1 };
if (MatrixUpdateVersion(__version__)) then return; end

-----------------------------------------------------------------------
-- Variables
-----------------------------------------------------------------------

local matrix_ignores = {
	__copy__ = true,
	__nodup__ = true,

	__var__ = true,
	__name__ = true,
	__nest__ = true,
	__mode__ = true,
	__lock__ = true,
	__last__ = true,
	__using__ = true,
	__super__ = true,
	__class__ = true,
	__handle__ = true,
	__deleted__ = true,
	__members__ = true,
	__children__ = true,

	__call = true,
	__index = true,
	__newindex = true,
	__tostring = true,

	Initial = true,
	Final = true,
	Super = true,
	New = true,
	FromHandle = true,
	Const = true,
	Static = true,
	Variant = true,
};

local enum_inherits = {
	Name = true,
};
local class_inherits = {
	__children__ = true,
	Name = true,
	Initial = true,
	Final = true,
	New = true,
	FromHandle = true,
	Const = true,
	Static = true,
	Variant = true,
};
local interface_inherits = {
	Name = true,
	Const = true,
	Variant = true,
	Static = true
};

local matrix_enums = { };
local matrix_classes = { };
local matrix_interfaces = { };

local last_noname_key = 0;

MATRIX_ENUM_NEXT = 0;

MATRIX_PRIVATE_ENUM = 0;
MATRIX_GLOBAL_ENUM = 1;

MATRIX_NORMAL_CLASS = 0;
MATRIX_VIRTUAL_CLASS = 1;
MATRIX_FINAL_CLASS = 2;

MATRIX_NONAME = "MATRIX_NONAME";

-----------------------------------------------------------------------
-- Local functions
-----------------------------------------------------------------------

local function Matrix_CopyMembers(node, members)
	local key, val;

	for key, val in pairs(members) do
		node.__members__[key] = val;
	end
end

local function Matrix_InheritProc(obj, ...)
	local proc;
	proc = rawget(obj, "__last__");
	if (proc) then
		obj.__last__ = nil;
		return proc(obj.__handle__, ...);
	end
end

local function Matrix_DefaultIndex(obj, key)
	local class, node;

	if (matrix_ignores[key]) then
		return nil;
	end
	class = getmetatable(obj);
	node = class.__members__[key];
	if (node) then
		if (node.type == 1) then
			local val = matrix.CopyValue(node.value);
			rawset(obj, key, val);
			return val;
		end
		return node.value;
	end
	if (obj.__handle__) then
		val = obj.__handle__[key];
		if (val) then
			rawset(obj, "__last__", val);
			return Matrix_InheritProc;
		end
	end
	return MatrixCommon[key];
end

local function Matrix_DefaultNewIndex(obj, key, value)
	local class, node;

	class = getmetatable(obj);
	node = class.__members__[key];
	if (node) then
		if (node.type == 2) then
			node.value = value;
			return;
		end
		if (node.type == 3) then
			matrix.Crash("ERROR_MODIFY_CONST", obj, key, value);
		end
	end
	rawset(obj, key, value);
end

local function Matrix_DefaultToString(obj)
	local class;

	class = getmetatable(obj);
	return class.__name__.."["..tostring(obj.__name__).."]";
end

-----------------------------------------------------------------------
-- Enum Meta Table
-----------------------------------------------------------------------

local enum_metatable = {
	__nodup__ = true,
	__index = function(enum, key)
		if (enum_inherits[key]) then
			return MatrixCommon[key];
		end
		if (enum.__members__[key]) then
			return enum.__members__[key];
		end
		if (matrix_ignores[key]) then
			return nil;
		end
		matrix.Crash("ERROR_NOT_FOUND", tostring(enum), key);
	end,
	__newindex = function(enum, key, val)
		if (enum.__members__[key]) then
			matrix.Crash("ERROR_NAME_CONFLICT", "EnumItem", key);
		end
		if (not matrix.IsKeyName(key)) then
			matrix.Crash("ERROR_BAD_NAME", "EnumItem", key);
		end
		if (val <= enum.__last__) then
			val = enum.__last__ + 1;
		end
		enum.__last__ = val;
		enum.__members__[key] = val;
		if (enum.__mode__ == MATRIX_GLOBAL_ENUM) then
			_G[key] = val;
		end
	end,
	__tostring = function(enum)
		return "Enum["..enum.__name__.."]";
	end
};

-------------------------------------------------------------------------------
-- Class Metatable
-------------------------------------------------------------------------------

local class_metatable = {
	__nodup__ = true,
	__call = function(class, ...)
		return class:New(...);
	end,
	__index = function(class, key)
		if (class_inherits[key]) then
			return MatrixCommon[key];
		end
		if (matrix_ignores[key]) then
			return nil;
		end
		matrix.Crash("ERROR_NOT_FOUND", tostring(class), key);
	end,
	__newindex = function(class, key, val)
		local node;

		if (matrix_ignores[key]) then
			rawset(class, key, val);
			return;
		end
		node = class.__members__[key];
		if (node and (node.owner == class)) then
			matrix.Crash("ERROR_MEMBER_CONFLICT", class, key, val);
		end
		node = { };
		node.type = 0;
		node.owner = class;
		node.value = val;
		class.__members__[key] = node;
	end,
	__tostring = function(class)
		return "Class["..class.__name__.."]";
	end,
};

-------------------------------------------------------------------------------
-- Interface Metatable
-------------------------------------------------------------------------------

local interface_metatable = {
	__nodup__ = true,
	__index = function(ifset, key)
		if (interface_inherits[key]) then
			return MatrixCommon[key];
		end
		matrix.Crash("ERROR_NOT_FOUND", ifset, key);
	end,
	__newindex = function(ifset, key, val)
		local node;

		if (matrix_ignores[key]) then
			matrix.Crash("ERROR_BAD_MEMBER", ifset, key, val);
		end
		node = ifset.__members__[key];
		if (node and (node.owner == ifset)) then
			matrix.Crash("ERROR_MEMBER_CONFLICT", ifset, key, val);
		end
		node = { };
		node.type = 0;
		node.owner = ifset;
		node.value = val;
		ifset.__members__[key] = node;
	end,
	__tostring = function(ifset)
		return "Interface["..ifset.__name__.."]";
	end,
};

-----------------------------------------------------------------------
-- matrix functions
-----------------------------------------------------------------------

matrix.IsIgnore = function(name)
	return matrix_ignores[name];
end

matrix.IsEnum = function(obj)
	return (getmetatable(obj) == enum_metatable);
end

matrix.IsClass = function(obj)
	return (getmetatable(obj) == class_metatable);
end

matrix.IsObject = function(obj)
	local mt = getmetatable(obj);
	return (mt and (getmetatable(mt) == class_metatable));
end

matrix.IsInterface = function(obj)
	return (getmetatable(obj) == interface_metatable);
end

matrix.GetEnum = function(name)
	return matrix_enums[name];
end

matrix.GetEnumCount = function()
	return #matrix_enums;
end

matrix.GetClass = function(name)
	return matrix_classes[name];
end

matrix.GetClassCount = function()
	return #matrix_classes;
end

matrix.GetInterface = function(name)
	return matrix_interfaces[name];
end

matrix.GetInterfaceCount = function()
	return #matrix_interfaces;
end

-----------------------------------------------------------------------
-- MatrixEnum
-----------------------------------------------------------------------

function MatrixEnum(name, mode, last)
	local node;

	if (not matrix.IsName(name)) then
		matrix.Crash("ERROR_BAD_NAME", "Enum", name);
	end
	if (matrix_enums[name]) then
		matrix.Crash("ERROR_NAME_CONFLICT", "Enum", name);
	end
	if (not mode) then
		mode = MATRIX_PRIVATE_ENUM;
	end
	if (not last) then
		last = 0;
	end
	node = { };
	node.__name__ = name;
	node.__mode__ = mode;
	node.__last__ = last;
	node.__members__ = { };
	setmetatable(node, enum_metatable);
	if (matrix.IsKeyName(name) and (string.sub(name, 1, 6) == "Matrix")) then
		_G[name] = node;
	end
	matrix_enums[name] = node;
	return node;
end

-------------------------------------------------------------------------------
-- MatrixClass
-------------------------------------------------------------------------------

function MatrixClass(name, super, mode, ...)
	local class, idx, ifsets;

	if (not matrix.IsName(name)) then
		matrix.Crash("ERROR_BAD_NAME", "Class", name);
	end
	if (matrix_classes[name]) then
		matrix.Crash("ERROR_NAME_CONFLICT", "Class", name);
	end
	if (not mode) then
		mode = MATRIX_NORMAL_CLASS;
	end
	if (not matrix.IsClass(super)) then
		matrix.Crash("ERROR_BAD_CLASS", tostring(super), name);
	elseif (super.__mode__ == MATRIX_FINAL_CLASS) then
		matrix.Crash("ERROR_EXTEND_FINAL", super:Name(), name);
	end

	class = { };
	class.__nodup__ = true;
	class.__name__ = name;
	class.__mode__ = mode;
	class.__super__ = super;
	class.__using__ = 0;
	class.__members__ = { };

	class.__index = Matrix_DefaultIndex;
	class.__newindex = Matrix_DefaultNewIndex;
	class.__tostring = Matrix_DefaultToString;
	super.__using__ = super.__using__ + 1;
	Matrix_CopyMembers(class, super.__members__);
	ifsets = { ... };
	for idx = 1, #ifsets, 1 do
		if (not matrix.IsInterface(ifsets[idx])) then
			matrix.Crash("ERROR_BAD_INTERFACE", ifsets[idx]);
		end
		ifsets[idx].__using__ = ifsets[idx].__using__ + 1;
		Matrix_CopyMembers(class, ifsets[idx].__members__);
	end
	setmetatable(class, class_metatable);
	while (super ~= nil) do
		if (rawget(super, "__children__")) then
			super.__children__[#(super.__children__) + 1] = class;
		end
		super = rawget(super, "__super__");
	end
	if (matrix.IsKeyName(name) and (string.sub(name, 1, 6) == "Matrix")) then
		_G[name] = class;
	end
	matrix_classes[name] = class;
	return class;
end

-------------------------------------------------------------------------------
-- MatrixInterface
-------------------------------------------------------------------------------

function MatrixInterface(name, ...)
	local node, idx, ifsets;

	if (not matrix.IsName(name)) then
		matrix.Crash("ERROR_BAD_NAME", "Interface", name);
	end
	if (matrix_interfaces[name]) then
		matrix.Crash("ERROR_NAME_CONFLICT", "Interface", name);
	end
	node = { };
	node.__name__ = name;
	node.__using__ = 0;
	node.__members__ = { };
	ifsets = { ... };
	for idx = 1, #ifsets, 1 do
		if (not matrix.IsInterface(ifsets[idx])) then
			matrix.Crash("ERROR_BAD_INTERFACE", ifsets[idx]);
		end
		Matrix_CopyMembers(node, ifsets[idx].__members__);
		ifsets[idx].__using__ = ifsets[idx].__using__ + 1;
	end
	setmetatable(node, interface_metatable);

	if (matrix.IsKeyName(name) and (string.sub(name, 1, 6) == "Matrix")) then
		_G[name] = node;
	end
	matrix_interfaces[name] = node;
	return node;
end

-------------------------------------------------------------------------------
-- MatrixObject
-------------------------------------------------------------------------------

MatrixObject = { };
MatrixObject.__nodup__ = true;
MatrixObject.__name__ = "MatrixObject";
MatrixObject.__mode__ = MATRIX_VIRTUAL_CLASS;
MatrixObject.__using__ = 0;
MatrixObject.__members__ = { };
MatrixObject.__index = Matrix_DefaultIndex;
MatrixObject.__newindex = Matrix_DefaultNewIndex;
MatrixObject.__tostring = Matrix_DefaultToString;
setmetatable(MatrixObject, class_metatable);
matrix_classes["MatrixObject"] = MatrixObject;

function MatrixObject:Initial(name)
	if (name == "MATRIX_NONAME") then
		last_noname_key = last_noname_key + 1;
		name = "__noname_obj"..tostring(last_noname_key).."__";
	elseif (not matrix.IsName(name)) then
		matrix.Crash("ERROR_BAD_NAME", "Object", name, self.__class__);
	end
	rawset(self, "__name__", name);
end

function MatrixObject:Final()
	self.__name__ = nil;
end
